
#include <htc.h>
#include <stdbool.h>

#define PIC_CLK 16000000
#include "delay.h"

#define MAX_VALUE 99*60 + 59
#define TIMER_FREQUENCY 15625

#define DELAY_SCHEMA1 100
#define DELAY_SCHEMA2 1000
#define DELAY_DISPLAY 10

#define BUZZ RC4

#define ALED RA2
#define BLED RC0
#define CLED RC1
#define DLED RC2
#define ELED RB4
#define FLED RB5
#define GLED RB6

#define DIGIT1 RC5
#define DIGIT2 RC7
#define DIGIT3 RC6
#define DIGIT4 RC3

unsigned int currentValue = 0;
unsigned int timerCount = 0;
unsigned long long secElapsed = 0;
unsigned long long oldElapsedMs = 0;
char oldRA5 = 0;
char ringing = 0;
char running = 0;

void init();
void playSound();
void displayValue(unsigned int v);
void showDigit(unsigned int v, char d);

void main() {
    init();
    
    while (1) {
        displayValue(currentValue);
    }
}

void init() {
    TRISA = 0; // RA are output, except:
    TRISA0 = TRISA1 = TRISA3 = 1; //  used for ICSP
    TRISA4 = TRISA5 = 1; // RA4 -> LSB rotary switch, RA5 -> 2LSB rotary switch
    
    TRISB = 0; // RB are output, except:
    TRISB7 = 1; // RB7 -> start button
    
    TRISC = 0; // RC are output
    
    // Use internal clock, no WDT, no power up timer, use MCLR, no code protection, no brown-out reset, CLOUT used a standard I/O
    __CONFIG(FOSC_INTOSC & WDTE_OFF & PWRTE_OFF & MCLRE_ON & CP_OFF & BOREN_OFF & CLKOUTEN_OFF);
    // No write protection, no reset on stack overflow, no low voltage programming
    __CONFIG(WRT_OFF & STVREN_OFF & LVP_OFF);
    
    // INTCON:
    TMR0IE = 1; // interrupt on timer0 overflow
    IOCIE = 1; // Activate general interrupt on change (IOC)
    
    // OPTION_REG
    TMR0CS = 0; // timer0 in timer mode
    PSA = 1; // no prescaler timeout = 16M/4/256 = 15625Hz
    
    OSCCON = 0b01111000; // select clock frequency (div of 16MHz) -> 1111 = 16MHz, 00->use clock from CONFIG1
    
    IOCAP4 = 1; // bit 4 of PORT A interrupts on rising edge
    IOCAN4 = 1; // bit 4 of PORT A interrupts on falling edge
    IOCBP7 = 1; // bit 7 of PORT B interrupts on rising edge
}

void interrupt isr() {
    if (IOCAF4) { // interrupt caused by change on PORTA.4
        IOCAF4 = 0;
        // switch rotated
        
        unsigned long long elapsedMs = 1000*secElapsed + 1000*timerCount/TIMER_FREQUENCY; // do not factorize the 1000* to prevent rounding
        unsigned long long diff = elapsedMs - oldElapsedMs;
        oldElapsedMs = elapsedMs;
        
        unsigned int value = 1;
        if (diff < 1) {
            value = 300;
        } else if (diff < 10) {
            value = 60;
        } else if (diff < 50) {
            value = 10;
        } else if (diff < 100) {
            value = 5;
        }
        
        if (RA4 && RA5 != oldRA5 && currentValue > 0) {
            currentValue -= value;
            
        } else if (RA4 && RA5 == oldRA5 && currentValue < MAX_VALUE) {
            currentValue += value;
            
        } else if (!RA4 && RA5 != oldRA5 && currentValue < MAX_VALUE) {
            currentValue += value;
            
        } else if (!RA4 && RA5 == oldRA5 && currentValue > 0) {
            currentValue -= value;
        }
        
        oldRA5 = RA5;
        
    } else if (IOCBF7) { // interrupt caused by change on PORTB.7
        IOCBF7 = 0;
        // Start button
        ringing = 0;
        
        timerCount = 0;
        running = (running+1)%2; // invert running
        
    } else if (TMR0IF) { // interrupt caused by timer0 overflow
        TMR0IF = 0;
        
        timerCount++;
        
        if (timerCount >= TIMER_FREQUENCY) {
            timerCount = 0;
            secElapsed++;

            if (running) {
                currentValue--;
                if (currentValue == 0) {
                    ringing = 1;
                    playSound();
                }
            }
        }
    }
}

void playSound() {
    char i;
    while (ringing) {
        i = 4;
        while (i--) {
            BUZZ = 1;
            DelayMs(DELAY_SCHEMA1);
            BUZZ = 0;
            DelayMs(DELAY_SCHEMA1);
        }
        DelayBigMs(DELAY_SCHEMA2);
    }
}

void displayValue(unsigned int value) {
    unsigned int n = value;
    unsigned int sec = n % 60;
    unsigned int min = n / 60;
    
    showDigit(min/10, 1);
    DelayMs(DELAY_DISPLAY);
    
    showDigit(min%10, 2);
    DelayMs(DELAY_DISPLAY);
    
    showDigit(sec/10, 3);
    DelayMs(DELAY_DISPLAY);
    
    showDigit(sec%10, 4);
    DelayMs(DELAY_DISPLAY);
}

void showDigit(unsigned int value, char digit) {
    DIGIT1 = DIGIT2 = DIGIT3 = DIGIT4 = 0;
    
    switch (digit) {
        case 1:
            DIGIT1 = 1;
            break;
        case 2:
            DIGIT2 = 1;
            break;
        case 3:
            DIGIT3 = 1;
            break;
        case 4:
            DIGIT4 = 1;
            break;
    }
    
    ALED = BLED = CLED = DLED = ELED = FLED = GLED = 0;
    
    switch (value) {
        case 1:
            BLED = CLED = 1;
            break;
        case 2:
            ALED = BLED = GLED = ELED = DLED = 1;
            break;
        case 3:
            ALED = BLED = GLED = CLED = DLED = 1;
            break;
        case 4:
            FLED = GLED = BLED = CLED = 1;
            break;
        case 5:
            ALED = FLED = GLED = CLED = DLED = 1;
            break;
        case 6:
            ALED = CLED = DLED = ELED = FLED = GLED = 1;
            break;
        case 7:
            ALED = BLED = CLED = 1;
            break;
        case 8:
            ALED = BLED = CLED = DLED = ELED = FLED = GLED = 1;
            break;
        case 9:
            ALED = BLED = CLED = DLED = FLED = GLED = 1;
            break;
        case 0:
            ALED = BLED = CLED = DLED = ELED = FLED = 1;
            break;
    }
}


